/*
 * timer.c
 *
 * Copyright (C) 2018 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <timer.h>
#include <thread.h>

volatile qword_t total_ticks = 0;

static dword_t timer_reload_value;

static void timer_irq(registers_t *regs, byte_t irq_num)
{
    total_ticks++;

    if (scheduler_enabled)
    {
        scheduler(regs);
        get_current_thread()->running_ticks++;
    }
}

qword_t timer_get_milliseconds(void)
{
    return total_ticks;
}

qword_t timer_get_nanoseconds(void)
{
    critical_t critical;
    enter_critical(&critical);

    qword_t result = total_ticks * 1000000ULL;

    cpu_write_port_byte(TIMER_CMD_PORT, 0x00);
    word_t count = cpu_read_port_byte(TIMER_CHANNEL_PORT(0));
    count |= cpu_read_port_byte(TIMER_CHANNEL_PORT(0)) << 8;

    ASSERT(count <= timer_reload_value);
    count = timer_reload_value - count;

    result += (count * 1000000000ULL) / (qword_t)TIMER_BASE_FREQUENCY;

    leave_critical(&critical);
    return result;
}

sysret_t syscall_query_timer(long reserved, timer_info_type_t info_type, void *buffer, size_t size)
{
    if (get_previous_mode() == USER_MODE && !check_usermode(buffer, size)) return ERR_BADPTR;
    if (size < sizeof(qword_t)) return ERR_SMALLBUF;
    qword_t value;

    switch (info_type)
    {
    case TIMER_MILLISECONDS:
        value = timer_get_milliseconds();
        break;

    case TIMER_NANOSECONDS:
        value = timer_get_nanoseconds();
        break;

    case TIMER_PRECISION:
        value = TIMER_BASE_FREQUENCY;
        break;

    default:
        return ERR_INVALID;
    }

    EH_TRY
    {
        *(qword_t*)buffer = value;
    }
    EH_CATCH
    {
        EH_ESCAPE(return ERR_BADPTR);
    }
    EH_DONE;

    return ERR_SUCCESS;
}

void timer_init(void)
{
    timer_reload_value = TIMER_BASE_FREQUENCY / TIMER_FREQUENCY;

    dword_t remainder = TIMER_BASE_FREQUENCY - timer_reload_value * TIMER_FREQUENCY;
    if ((remainder * 2) > TIMER_FREQUENCY) timer_reload_value++;

    if (timer_reload_value == 0) timer_reload_value = 1;
    if (timer_reload_value >= 65536) timer_reload_value = 0;

    cpu_write_port_byte(TIMER_CMD_PORT, TIMER_RATE_GENERATOR(0));
    cpu_write_port_byte(TIMER_CHANNEL_PORT(0), timer_reload_value & 0xFF);
    cpu_write_port_byte(TIMER_CHANNEL_PORT(0), (timer_reload_value >> 8) & 0xFF);
    register_irq_handler(TIMER_IRQ, &timer_irq, FALSE);
}
